﻿/**

 * Copyright (c) 2015-2016, FastDev 刘强 (fastdev@163.com) & Quincy.

 *

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 *

 *      http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */

using MsgPack.Serialization;
using OF.Cache2.Entity;
using StackExchange.Redis;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using OF.Cache2.Core;
using OF.Cache2.Entity;

namespace OF.Cache2.Redis
{
    //Note:服务器时间保持一致才行（redis 服务端，不然过期时间会有问题）
    public class RedisHelper
    {
        private static string SepChar = new string((char)0x01, 1);
        private static object _locker = new object();
        private static ConnectionMultiplexer _redis = null;
        private static readonly byte[] EmptyBytes = new byte[0];
        private static bool isConnectionFailed = false;
        private static bool isInited = false;
        private static DateTime UnInitedFailedTime = DateTime.Now;
        private const int UnInitedRetrySeconds = 5;

        public static bool IsConnectionOk()
        {
            if (isConnectionFailed)
            {
                if (!isInited)
                {
                    if (DateTime.Now.Subtract(UnInitedFailedTime).TotalSeconds > UnInitedRetrySeconds)
                    {
                        UnInitedFailedTime = DateTime.Now;
                        isConnectionFailed = false;
                        Console.WriteLine("init faield retry!");
                    }
                }
            }
            bool isOk = !isConnectionFailed;
            return isOk;
        }

        private static void SetupEventHandler(ConnectionMultiplexer manager)
        {
            manager.ConnectionFailed += manager_ConnectionFailed;
            manager.ConnectionRestored += manager_ConnectionRestored;
            manager.ErrorMessage += manager_ErrorMessage;
            manager.InternalError += manager_InternalError;
        }

        static void manager_ConnectionRestored(object sender, ConnectionFailedEventArgs e)
        {
            if (_redis != null)
            {
                lock (_locker)
                {
                    if (_redis != null)
                    {
                        isConnectionFailed = false;
                        DoDispose();
                    }
                }
            }
        }

        static void manager_InternalError(object sender, InternalErrorEventArgs e)
        {
            Console.WriteLine(e.Exception);
        }

        static void manager_ErrorMessage(object sender, RedisErrorEventArgs e)
        {
            Console.WriteLine(e.Message);
        }

        static void manager_ConnectionFailed(object sender, ConnectionFailedEventArgs e)
        {
            isConnectionFailed = true;
        }

        public static ConnectionMultiplexer Manager
        {
            get
            {
                if (_redis == null)
                {
                    lock (_locker)
                    {
                        if (_redis != null)
                        {
                            return _redis;
                        }
                        else
                        {
                            try
                            {
                                _redis = GetManager();
                                isConnectionFailed = false;
                                SetupEventHandler(_redis);
                                isInited = true;
                                return _redis;
                            }
                            catch (Exception ex)
                            {
                                isConnectionFailed = true;
                                if (!isInited)
                                {
                                    UnInitedFailedTime = DateTime.Now;
                                }
                                return _redis;
                            }
                        }
                    }
                }
                else
                {
                    return _redis;
                }
            }
        }

        private static string GetDefaultConnectionString()
        {
            return "localhost";
        }

        private static ConnectionMultiplexer GetManager(string connectionString = null)
        {
            if (string.IsNullOrEmpty(connectionString))
            {
                connectionString = GetDefaultConnectionString();
            }
            return ConnectionMultiplexer.Connect(connectionString);
        }

        public static string MergeKey(params string[] keys)
        {
            return string.Join(SepChar, keys);
        }

        public static byte[] CombineKeyData(string key, byte[] data)
        {
            if (data == null)
            {
                data = EmptyBytes;
            }
            byte[] keyBytes = System.Text.Encoding.UTF8.GetBytes(key);
            byte[] keyDataBytes = new byte[keyBytes.Length + 1 + data.Length];
            Array.Copy(keyBytes, keyDataBytes, keyBytes.Length);
            keyDataBytes[keyBytes.Length] = 0;
            if (data.Length > 0)
            {
                Array.Copy(data, 0, keyDataBytes, keyBytes.Length + 1, data.Length);
            }
            return keyDataBytes;
        }

        public static Tuple<string, byte[]> SplitKeyData(byte[] keyDataBytes)
        {
            int keyLen = Array.IndexOf<byte>(keyDataBytes, 0);
            byte[] keyBytes = new byte[keyLen];
            Array.Copy(keyDataBytes, keyBytes, keyLen);
            string key = System.Text.Encoding.UTF8.GetString(keyBytes);
            int dataLen = keyDataBytes.Length - key.Length - 1;
            byte[] data = null;
            if (dataLen > 0)
            {
                data = new byte[dataLen];
                Array.Copy(keyDataBytes, key.Length + 1, data, 0, data.Length);
            }
            return new Tuple<string, byte[]>(key, data);
        }

        public static void DoDispose()
        {
            try
            {
                if (_redis != null)
                {
                    _redis.Dispose();
                    _redis = null;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
    }
}
